-
-
Notifications
You must be signed in to change notification settings - Fork 261
Emit POSTINITIALIZE notification after init()
#1211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ba02ea9 to
0812e49
Compare
|
Thanks!
There are many more changes than these two. Please edit your initial description to elaborate what exactly the PR does and its motivation. Follow-up to #1208. |
|
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1211 |
0812e49 to
14d9878
Compare
|
I'm relatively new to gdext. It seems that we can't modify the object itself (e.g. |
14d9878 to
99e092a
Compare
|
Part of that is what I'm looking into in #557. This PR seems to integrate with Godot's own POST_INIT notification. Can you demonstrate what it enables with some code examples? Please edit the initial post. |
POSTINITIALIZE notification after init()
|
Unfortunately, I encountered a crash when using #[derive(GodotClass)]
#[class(base=ArrayMesh,init,tool)]
pub struct EmptyTriangleMesh {
base: Base<ArrayMesh>,
}
#[godot_api]
#[allow(unused_variables)]
impl IArrayMesh for EmptyTriangleMesh {
fn on_notification(&mut self, what: notify::ObjectNotification) {
if what == notify::ObjectNotification::POSTINITIALIZE {
let mut arrays = VariantArray::new();
arrays.resize(
mesh::ArrayType::MAX.ord().try_into().unwrap(),
&Variant::nil(),
);
let indices = PackedInt32Array::from([0, 1, 2]);
arrays.set(
mesh::ArrayType::INDEX.ord().try_into().unwrap(),
&indices.to_variant(),
);
self.base_mut()
.add_surface_from_arrays_ex(mesh::PrimitiveType::TRIANGLES, &arrays)
.flags(mesh::ArrayFormat::FLAG_USES_EMPTY_VERTEX_ARRAY)
.done();
}
}
fn get_surface_count(&self) -> i32 {
todo!()
}
fn surface_get_array_len(&self, index: i32) -> i32 {
todo!()
}
fn surface_get_array_index_len(&self, index: i32) -> i32 {
todo!()
}
fn surface_get_arrays(&self, index: i32) -> VariantArray {
todo!()
}
fn surface_get_blend_shape_arrays(&self, index: i32) -> Array<VariantArray> {
todo!()
}
fn surface_get_lods(&self, index: i32) -> Dictionary {
todo!()
}
fn surface_get_format(&self, index: i32) -> u32 {
todo!()
}
fn surface_get_primitive_type(&self, index: i32) -> u32 {
todo!()
}
fn surface_set_material(&mut self, index: i32, material: Option<Gd<Material>>) {
todo!()
}
fn surface_get_material(&self, index: i32) -> Option<Gd<Material>> {
todo!()
}
fn get_blend_shape_count(&self) -> i32 {
todo!()
}
fn get_blend_shape_name(&self, index: i32) -> StringName {
todo!()
}
fn set_blend_shape_name(&mut self, index: i32, name: StringName) {
todo!()
}
fn get_aabb(&self) -> Aabb {
todo!()
}
}So I must be missing something. |
|
Hmm, interesting – it seems that our refcount is still unset before we pass it to godot (after "create" function will yield the result). I checked what godot-cpp is doing, and it seems that their flow is the same? (create instance -> construct object 2 -> set instance -> set instance binding -> and finally, a postinitialize). https://github.com/godotengine/godot-cpp/blob/6a870949a5d38c62638af23dc13210f77154aa2c/include/godot_cpp/core/class_db.hpp#L124 |
|
Surprisingly, I tested the following code on godot-cpp-template: #include "example_class.h"
void ExampleClass::_bind_methods() {
godot::ClassDB::bind_method(D_METHOD("print_type", "variant"), &ExampleClass::print_type);
}
void ExampleClass::_notification(int p_what) {
if (p_what == NOTIFICATION_POSTINITIALIZE) {
Ref<ExampleClass> t = this;
UtilityFunctions::print("ref count: ", t->get_reference_count());
}
}
void ExampleClass::print_type(const Variant &p_variant) const {
print_line(vformat("Type: %d", p_variant.get_type()));
}extends Node
func _ready() -> void:
var example := ExampleClass.new()
example.print_type(example)It prints a error that indicates But if I remove So could this be a issue with Godot? Edit: Additionally, obtaining |
|
Yep, it seems to be a problem with Godot itself – such behavior in |
|
@beicause you closed godotengine/godot#108395 with:
Given that, does it make sense to support If yes, what is the use case? In case we decide not to implement |
|
POSTINITIALIZE may be useful, as it allows base classes to execute logic after subclass initialization - for example Control updates its theme cache during POSTINITIALIZE. And godotengine/godot#91018 (comment) 🤔 If #557 gets resolved, could the same solution enable POSTINITIALIZE to access base class? |
|
I see... Then we would need to emit it. But how to handle it properly in |
One silly idea I had was using deferred refcount with Very silly hack, it adds some, potentially pretty serious, overhead, but hey, it works |
|
I've used a hack to avoid releasing RefCounted in this PR: let mut obj = Gd::<T>::from_obj_sys_weak(object_ptr).upcast_object();
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
std::mem::forget(obj); |
It is not a hack, it is totally sensible thing to do – don't increase the refcount while passing an object to the user internally. What I've been thinking is something along lines of: if let Ok(object_ptr) = create_custom(T::__godot_user_init) {
let mut obj = Gd::<T>::from_obj_sys_weak(object_ptr).upcast_object();
if T::inherits::<RefCounted>() {
<classes::Object as Bounds>::DynMemory::maybe_inc_ref(&mut obj.raw);
obj.linked_callable("deferred refcount", move |_| {
// Run RawGd<T> destructor.
drop(Gd::<T>::from_obj_sys_weak(object_ptr));
Ok(Variant::nil())
}).call_deferred(&[]);
}
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
std::mem::forget(obj);
object_ptrIt works but is very janky – instance is being destroyed at the end of a frame, not imminently, appending callable to every RefCounted has some inherent overhead etc – thus if we resort to such a hack, it should be explicitly called by user (by invoking base for the first time before Don't actually implement it – firstly we need to reason if it is sensible thing to do, check for potential side effects etc 😅 |
|
This seems fragile. There's no guarantee the object remains alive when the callable executes. I'd prefer having something like |
I don't like it either. It is choose-yer-poison situation (on another hand nothing better comes to my mind).
…and objects staying alive longer than they should are not perfect either.
Yeah, I would prefer it as well – unfortunately a lot of interactions with Godot require increasing the RefCount, thus making this way inherently unsound (but it would allow user to "route" calls via I'll underline – I'm not (and don't strive to be) authoritative in this matter, it is just one possible solution 🤷 |
Weak ref doesn't keep the object alive though -- that's the point. |
It's true. Even if it is unsound, it's still better than nothing. Why can't we ever provide unsafe APIs? |
First, unsafe != unsound. The latter means there's a violation of Rust's memory safety principles. In other words, a bug that must be fixed. Obviously we can provide That said, I didn't test your code. It is possible it works well. Scenarios that we need to protect against:
And panic in these cases is good enough. But UB (undefined behavior) isn't. |
I think there's still a way to check if the object is alive: saving its ObjectID and using |
|
Sorry for the long waiting period. I was working on #1273 -- not sure if this has an impact on how we would approach this issue. The current implementation used there (deferred unref of |
779c043 to
ddc5b76
Compare
Bromeon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the great work so far.
I think it would be good to split the test_notifications itest into two tests, one for manually-managed objects (like now), plus a new one with a RefCounted base. This would make sure we don't trigger UB/leaks due to the "access base during init" problem.
99e092a to
7a7ffdc
Compare
This comment was marked as resolved.
This comment was marked as resolved.
5ed6063 to
04af742
Compare
Bromeon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding the RefCounted lifecycle test! 👍
47aa09d to
d34855c
Compare
Bromeon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks mostly good, 1 small change and should be ready! 🙂
d34855c to
f9e7576
Compare
|
Thanks a lot, and congrats to your first merged contribution! 🎉 |
Changes:
classdb_construct_object->classdb_construct_object2string_new_with_utf8_chars_and_len->string_new_with_utf8_chars_and_len2After this PR,
NOTIFICATION_POSTINITIALIZEwill be sent afterinit()and can be received correctly inon_notification()in godot 4.4 or later. See alsotest_notificationsin the itest.Partially addresses #557